set = {}
local mt = {} 	-- metatable

local function new (s)
	local set = {}

	setmetatable(set, mt)
	for _, v in ipairs(s) do
		set[v] = true
	end

	return set
end

set.new = new

function set.union (a, b)
	local res = set.new{}

	if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
		error("attmpt to 'add' a set with a non-set value", 2)
	end

	for k in pairs(a) do res[k] = true end
	for k in pairs(b) do res[k] = true end

	return res
end

function set.intersection (a, b)
	local res = set.new{}

	if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
		error("attmpt to 'mul' a set with a non-set value", 2)
	end

	for k in pairs(a) do
		res[k] = b[k] 
	end

	return res
end

function set.tostring (set)
	local l = {}

	for e in pairs(set) do
		l[#l + 1] = tostring(e)
	end

	return "{" .. table.concat(l, ", ") .. "}"
end

mt.__add = set.union
mt.__mul = set.intersection
mt.__le = function (a, b) 	-- subset
	for k in pairs(a) do 
		if not b[k] then return false end
	end
	return true
end
mt.__lt = function (a, b) 	-- proper subset
	return a <= b and not (b <= a)
end
mt.__eq = function (a, b)
	return a <= b and b <= a
end
mt.__tostring = set.tostring

mt.__metatable = "not your business" 	-- protect our metatable
																			-- user call setmetatable and getmetatable will error

return set
